/**
 * Copyright © 2013 Creativor Studio.
 * About license information, please see LICENSE.txt.
 */
package org.creativor.rayson.share.transport.nio;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;

import org.creativor.rayson.share.annotation.ThreadSafe;
import org.creativor.rayson.share.transport.BufferFormatException;
import org.creativor.rayson.share.transport.BufferParser;
import org.creativor.rayson.util.LangUtil;

/**
 * An data buffer or NIO reading.
 * 
 * @author creativor
 */
@ThreadSafe(false)
public class ReadBuffer {

	private final ByteBuffer bb;
	private final SocketChannel channel;
	private final int capacity;
	private int consumedIndex;
	private final BufferParser consumer;

	/**
	 * @param capacity
	 */
	public ReadBuffer(final ByteBuffer byteBuffer, final SocketChannel channel,
			final BufferParser consumer) {
		LangUtil.testNullArgument(byteBuffer, "Byte buffer");
		LangUtil.testNullArgument(channel, "Socket channel");
		this.consumer = consumer;
		this.channel = channel;
		this.capacity = byteBuffer.capacity();
		bb = byteBuffer;
		consumedIndex = 0;
	}

	/**
	 * Try to consume buffered data of this buffer.
	 * 
	 * @throws BufferFormatException
	 *             If the buffer data format is wrong.
	 * @return Consumed data count. 0 means that no data consumed. -1 means that
	 *         all the buffered data is consumed.
	 */
	public int consume() throws BufferFormatException {
		if (consumedIndex == bb.position())
			return -1;
		final int oldPos = bb.position();
		bb.limit(oldPos);
		bb.position(consumedIndex);
		int count = 0;
		while (bb.hasRemaining()) {
			if (!consumer.consume(bb.get()))
				break;
			count++;
		}
		consumedIndex += count;
		bb.limit(capacity);
		bb.position(oldPos);
		return count;
	}

	/**
	 * Fill this buffer from the underling socket channel.
	 * 
	 * 
	 * @return The number of bytes read, possibly zero, or -1 if the channel has
	 *         reached end-of-stream.
	 * @throws IOException
	 *             Signals that an I/O exception has occurred.
	 */
	public int fill() throws IOException {
		return channel.read(bb);
	}

	/**
	 * @return the capacity
	 */
	public final int getCapacity() {
		return capacity;
	}

	/**
	 * Tells whether there are any data in this buffer. </p>
	 * 
	 * @return <tt>true</tt> if, and only if, there is at least one byte
	 *         remaining in this buffer
	 */
	public final boolean hasRemaining() {
		return bb.hasRemaining();
	}

	/**
	 * Try to compact this buffer if this buffer has no remaining.
	 */
	public void tryCompact() {
		if (hasRemaining())
			return;
		// Do compact this buffer.
		consumer.compactPositions();
		bb.position(consumedIndex);
		bb.compact();
		bb.position(bb.limit() - consumedIndex);
		consumedIndex = 0;
	}
}
